package top.eleven.sixpence.generaterollbacksql.processor;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import top.eleven.sixpence.generaterollbacksql.common.CommonConstant;
import top.eleven.sixpence.generaterollbacksql.common.util.FileUtil;
import top.eleven.sixpence.generaterollbacksql.convertor.BaseConvertor;
import top.eleven.sixpence.generaterollbacksql.convertor.ConvertorManager;
import top.eleven.sixpence.generaterollbacksql.exception.EmptyParamException;
import top.eleven.sixpence.generaterollbacksql.exception.NeedBackupManualException;
import top.eleven.sixpence.generaterollbacksql.exception.NeedConfirmException;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.InputStreamReader;
import java.nio.file.Files;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

/**
 * <b>注意：</b>
 * <p>
 * 提供需要进行转换的sql，尽量保持单行，否则会降低效率
 * 即一条sql只占一行
 * </p>
 * 现支持以下SQL的转换
 *    <b>CREATE TABLE 、</b>
 *    <b>ALERT TABLE 、</b>
 *    <b>INSERT INTO</b>
 *
 * @author  eleven
 */
public class RollbackSqlProcessor {
    private static final Logger LOGGER = LoggerFactory.getLogger(RollbackSqlProcessor.class);
    private static final String CONVERTOR_NOT_FOUND = "convertor not found";
    private static final String ORIGINAL_SQL = "/*\n * ORIGINAL_SQL\n*/";

    /**
     * 多行注释标识
     */
    public Boolean multilineFlag;

    private final String outputFilePath;

    private final String outputFileName;

    private final String convertFailedSqlFile;

    private final String needBackupDataManual;


    private final String needConfirmSqlFile;

    private final ConvertorManager convertorManager;

    private final ProcessorProperties properties;

    public RollbackSqlProcessor() {
        this.multilineFlag = Boolean.FALSE;
        this.convertorManager = ConvertorManager.build();
        this.properties = ProcessorProperties.build();
        this.outputFilePath = properties.getOutputFilePath();
        this.outputFileName = properties.getOutputFileName();
        this.needBackupDataManual = properties.getNeedBackupDataManual();
        this.needConfirmSqlFile = properties.getNeedBackupDataManual();
        this.convertFailedSqlFile = properties.getConvertFailedSqlFile();
    }


    public void process(String sqlFile, String outputFilePath) throws FileNotFoundException {
        checkParam(sqlFile, outputFilePath);
        readSqlFileAndGenerateRollbackSql(sqlFile);
    }


    private void readSqlFileAndGenerateRollbackSql(String sqlFile) throws FileNotFoundException {
        File file = checkFile(sqlFile);
        try (BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(Files.newInputStream(file.toPath()), CommonConstant.ENCODING_UTF8))){
                StringBuilder rollbackSql = new StringBuilder();
                String lineTxt;
                int count = 0;
                while ((lineTxt = bufferedReader.readLine()) != null) {
                    if(isComment(lineTxt)) {
                        continue;
                    }

                    //若sql未在一行内，读取多行，直到读到完整sql
                    StringBuilder sb = null;
                    if (!lineTxt.endsWith(CommonConstant.SEMICOLON)) {
                        sb = new StringBuilder(lineTxt.replace( CommonConstant.NEW_LINE, CommonConstant.EMPTY_STRING));
                        while (!lineTxt.endsWith(CommonConstant.SEMICOLON) && (lineTxt = bufferedReader.readLine()) != null){
                            sb.append(lineTxt.replace(CommonConstant.NEW_LINE, CommonConstant.EMPTY_STRING));
                        }
                    }
                    sb = Optional.ofNullable(sb).orElse(new StringBuilder(lineTxt));

                    //解析sql并转换
                    String convertedSql = convert(sb.toString());
                    if (convertedSql.length() > 0){
                        LOGGER.info(convertedSql);
                        rollbackSql.append(convertedSql);
                        count++;
                    }

                    //每生成64行回滚sql空5行，方便查看，若无该需求可注释， 并输出一次结果
                    if(count > 0 && (count & 63) == 0 ){
                        rollbackSql.append(CommonConstant.NEW_LINE)
                                .append(CommonConstant.NEW_LINE);
                        saveConvertSql(rollbackSql.toString());
                        rollbackSql.setLength(0);
                    }

                }
                saveConvertSql(rollbackSql.toString());
                LOGGER.info(String.format("%s SQL successfully converted to rollback script!", count));

        } catch (Exception e) {
            LOGGER.error("convert sql failed!", e);
        }
    }

    /**
     * 根据不同的sql，生成对应的回滚语句
     * 未支持的sql，可根据自己需要
     *
     * @param sql
     * @return
     */
    private  String convert(String sql){
        try {
            Set<Map.Entry<String, Object>> entries =  convertorManager.getConvertors().entrySet();
            for (Map.Entry<String, Object> next : entries) {
                if (sql.startsWith(next.getKey())) {
                    return ((BaseConvertor) next.getValue()).convert(sql);
                }
            }
            saveConvertFailedSql(sql, CONVERTOR_NOT_FOUND, this.convertFailedSqlFile);
        } catch (NeedConfirmException e1){
            saveSqlNeedConfirm(sql, e1.getMessage());
        } catch (NeedBackupManualException e2){
            saveConvertFailedSql(sql, e2.getMessage(), this.needBackupDataManual);
        } catch (Exception e3) {
            LOGGER.warn("convert sql filed!");
            saveConvertFailedSql(sql, e3.getMessage(), this.convertFailedSqlFile);
        }
        return CommonConstant.EMPTY_STRING;
    }

    /**
     * 校验输入参数是否为空
     *
     * @param params
     */
    private void checkParam(String ... params) {
        for (String param : params) {
            if(CommonConstant.EMPTY_STRING.equals(param) || null == param){
                String message = "param is empty, please check it!";
                LOGGER.info(message);
                throw new EmptyParamException(message);
            }
        }
    }

    /**
     * 校验文件是否存在以及是否文件而非文件夹
     *
     * @param sqlFile
     * @return
     * @throws FileNotFoundException
     */
    private File checkFile(String sqlFile) throws FileNotFoundException {
        File file = new File(sqlFile);
        if(!file.exists() && file.isDirectory()){
            String message = String.format("%s is not exist or it is a directory!", sqlFile);
            LOGGER.info(message);
            throw new FileNotFoundException(message);
        }
        return file;
    }


    /**
     * 校验当前行是否为注释
     *
     * @param comment 当前行数据
     * @return Boolean true--->当前行为注释  false--->当前行不为注释
     */
    private Boolean isComment(String comment) {
        if(comment.trim().equals(CommonConstant.EMPTY_STRING)){
            return true;
        }

        //若当前已是多行注释，寻找多行注释结尾标识
        //找到结尾标识，返回将多行注释标记为false，反之继续
        if (this.multilineFlag){
            this.multilineFlag = !comment.endsWith(properties.getMultilineAnnotatorSuf());
            return true;
        }

        //判断是否是多行注释
        if(comment.startsWith(properties.getMultilineAnnotatorPre())){
            this.multilineFlag = true;
            return true;
        }

        //判断单行注释
        for (int i = 0; i < properties.getSingleAnnotator().length; i++) {
            if(comment.startsWith(properties.getSingleAnnotator()[i])){
                return true;
            }
        }

        return false;
    }

    /**
     * 保存转换成功sql
     * @param sql
     */
    private void saveConvertSql(String sql){
        FileUtil.outputFile(sql, this.outputFilePath, this.outputFileName);
    }

    /**
     * 保存转换失败sql
     * @param sql
     */
    private void saveConvertFailedSql(String sql, String reason, String fileName){
        StringBuilder data = new StringBuilder(properties.getMultilineAnnotatorPre())
                .append(reason)
                .append(properties.getMultilineAnnotatorSuf())
                .append(CommonConstant.NEW_LINE)
                .append(sql);
        printData2Console(data.toString());
        FileUtil.outputFile(data.toString(),this.outputFilePath, fileName);
    }

    private void saveSqlNeedConfirm(String sql, String reason){
        StringBuilder data = new StringBuilder(ORIGINAL_SQL);
        data.append(sql).append(reason);
        FileUtil.outputFile(data.toString(),this.outputFilePath, this.needConfirmSqlFile);
    }

    private void printData2Console(String data) {
        LOGGER.warn("==========================data===========================");
        LOGGER.warn(data);
        LOGGER.warn("=========================================================");

    }

}
